﻿using System;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.UI;
using EPiServer;
using EPiServer.Configuration;
using EPiServer.Core;
using EPiServer.DataAccess;
using EPiServer.Security;
using EPiServer.SpecializedProperties;
using EPiServer.Web;
using PageTypeBuilder;
using TemplateFoundation.Exceptions;
using TemplateFoundation.PageTypes;

namespace TemplateFoundation.Core
{
    /// <summary>
    /// Extension methods for EPiServer Template Foundation
    /// </summary>
    /// <remarks>These are mostly related to PageData objects such as PageTypeBase</remarks>
    public static class Extensions
    {
        #region EPiServer extensions

        /// <summary>
        /// Returns the page associated with this page link
        /// </summary>
        /// <param name="pageLink"></param>
        /// <returns></returns>
        public static PageTypeBase GetPage(this PageReference pageLink)
        {
            return pageLink.GetPage(null);
        }

        /// <summary>
        /// Returns the page associated with this page link
        /// </summary>
        /// <param name="pageLink">The page link.</param>
        /// <param name="languageSelector">The language branch.</param>
        /// <returns></returns>
        public static PageTypeBase GetPage(this PageReference pageLink, ILanguageSelector languageSelector)
        {
            if (PageReference.IsNullOrEmpty(pageLink))
            {
                throw new TemplateFoundationException("Unable to get page", new ArgumentNullException("pageLink", "Page link was empty"));
            }

            var page = languageSelector != null
                       ? DataFactory.Instance.GetPage(pageLink, languageSelector)
                       : DataFactory.Instance.GetPage(pageLink);

            if (!(page is PageTypeBase))
            {
                throw new TemplateFoundationException("Page reference does not represent a page of type 'PageTypeBase'. Page with ID '" + pageLink.ID + "' is of page type '" + page.PageTypeName + "' with page type ID '" + page.PageTypeID + "'.");
            }

            return (PageTypeBase)page;
        }

        /// <summary>
        /// Returns the page associated with this page link
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="pageLink"></param>
        /// <returns></returns>
        public static T GetPage<T>(this PageReference pageLink) where T : PageTypeBase
        {
            try
            {
                if(pageLink==PageReference.RootPage)
                {
                    return null;
                }

                return (T)DataFactory.Instance.GetPage(pageLink);
            }
            catch (Exception ex)
            {
                if (pageLink==PageReference.StartPage)
                {
                    throw new TemplateFoundationException(string.Format("The start page does not inherit '{0}'", typeof(StartPageBase).Name), ex);
                }
                
                throw new TemplateFoundationException(string.Format("Page reference does not represent a page of type '{0}'", typeof (T).Name), ex);
            }
        }

        /// <summary>
        /// Returns the current root page, the page beneath the start page in the specified page's path
        /// </summary>
        /// <remarks>If currently on the start page, or on a page outside the site structure, the start page is returned</remarks>
        public static PageTypeBase GetRootPage(this PageData page)
        {
            var currentRootPage = page as PageTypeBase;

            if (currentRootPage==null)
            {
                throw new TemplateFoundationException("The specified page is not a Template Foundation page type", new ArgumentException("Page must inherit from PageTypeBase","page"));
            }

            if (page.ParentLink == PageReference.StartPage || page.ParentLink==PageReference.RootPage)
            {
                return currentRootPage; // Current page is the root page
            }

            var pagePath = DataFactory.Instance.GetParents(page.PageLink);

            if (pagePath.Count>=2 && pagePath.Last()==PageReference.RootPage)
            {
                currentRootPage = pagePath[pagePath.Count - 2].GetPage(); // Get the first page below the root page
            }

            if (pagePath.Count>=3)
            {
                currentRootPage = pagePath[pagePath.Count - 3].GetPage(); // Get the first page below the start page
            }

            if (currentRootPage==null)
            {
                currentRootPage = PageReference.StartPage.GetPage(); // For pages outside the site structure
            }

            return currentRootPage;
        }

        /// <summary>
        /// Gets the friendly URL of the page
        /// </summary>
        /// <param name="page"></param>
        /// <returns></returns>
        public static string GetFriendlyUrl(this PageData page)
        {
            return page.GetFriendlyUrl(false);
        }

        /// <summary>
        /// Gets the friendly URL of the page
        /// </summary>
        /// <param name="page"></param>
        /// <param name="includeHost">Set to true to include the site's host name in the URL</param>
        /// <returns></returns>
        public static string GetFriendlyUrl(this PageData page, bool includeHost)
        {
            if(page==null)
            {
                throw new ArgumentNullException("page", "No page object specified");
            }

            if(Global.UrlRewriteProvider==null) // Can occur when executed outside EPiServer context, common in unit testing
            {
                return includeHost ? "http://tempuri.org/" + page.URLSegment : page.URLSegment;
            }
            
            var internalUrl = page.LinkURL;

            var url = new UrlBuilder(internalUrl);

            Global.UrlRewriteProvider.ConvertToExternal(url, page.PageLink, UTF8Encoding.UTF8);

            if (includeHost)
            {
                return string.Concat(Settings.Instance.SiteUrl.GetLeftPart(UriPartial.Authority), url.Uri.OriginalString);
            }

            return url.Uri.OriginalString;
        }

        /// <summary>
        /// Returns the friendly URL based on a specified internal URL
        /// </summary>
        /// <param name="internalUrl"></param>
        /// <returns></returns>
        public static string GetFriendlyUrl(string internalUrl)
        {
            var url = new UrlBuilder(internalUrl);

            Global.UrlRewriteProvider.ConvertToExternal(url, null, UTF8Encoding.UTF8);

            return url.Uri.OriginalString;
        }

        /// <summary>
        /// Converts a virtual path within the site to a complete URL including host name
        /// </summary>
        /// <param name="virtualPath"></param>
        /// <returns></returns>
        public static string GetFullUrl(string virtualPath)
        {
            if (!virtualPath.StartsWith("/"))
            {
                throw new TemplateFoundationException("Incorrect virtual path submitted", new ArgumentException("Virtual path must start with a leading slash", "virtualPath"));
            }

            var host = Settings.Instance.SiteUrl.GetLeftPart(UriPartial.Authority);

            return string.Format(
                "{0}{1}",
                host,
                virtualPath);
        }

        /// <summary>
        /// Gets the complete URL including host name
        /// </summary>
        /// <param name="link"></param>
        /// <returns></returns>
        public static string GetUrl(this LinkItem link)
        {
            string siteUrl = Settings.Instance.SiteUrl.ToString();

            if (siteUrl.EndsWith("/"))
            {
                siteUrl = siteUrl.Substring(0, siteUrl.Length - 1);
            }

            string linkUrl;

            if (PermanentLinkMapStore.TryToMapped(link.Href, out linkUrl))
            {
                return string.Concat(siteUrl, linkUrl);
            }
            else
            {
                return string.Empty;
            }
        }

        /// <summary>
        /// Returns the simple address of the page, or the standard URL if no simple address exists
        /// </summary>
        /// <param name="page"></param>
        /// <returns></returns>
        public static string GetSimpleAddress(this PageData page)
        {
            string simpleAddress = page["PageExternalURL"] as string ?? string.Empty;

            if (string.IsNullOrEmpty(simpleAddress))
            {
                return page.LinkURL;
            }

            return string.Concat("/", simpleAddress);
        }

        /// <summary>
        /// Returns the canonical URL of the page
        /// </summary>
        /// <param name="page"></param>
        /// <returns></returns>
        public static string GetCanonicalUrl(this PageData page)
        {
            return page.GetCanonicalUrl(false);
        }

        /// <summary>
        /// Gets the canonical URL for the page
        /// </summary>
        /// <param name="page">The page.</param>
        /// <param name="includeHost">if set to <c>true</c> [include host].</param>
        /// <returns></returns>
        public static string GetCanonicalUrl(this PageData page, bool includeHost)
        {
            return page.GetCanonicalUrl(includeHost, false);
        }

        /// <summary>
        /// Gets the canonical URL for the page
        /// </summary>
        /// <param name="page">The page.</param>
        /// <param name="includeHost">if set to <c>true</c> [include host].</param>
        /// <param name="friendlyUrl">if set to <c>true</c> [friendly URL].</param>
        /// <returns></returns>
        public static string GetCanonicalUrl(this PageData page, bool includeHost, bool friendlyUrl)
        {
            string url = string.Empty;

            switch (page.LinkType)
            {
                case PageShortcutType.FetchData: // If data is from another page
                    return ((PageReference) page["PageShortcutLink"]).GetPage().LinkURL;
                default:
                    // If fallback language the original language version's URL is the canonical
                    if(HttpContext.Current!=null)
                    {
                        string requestedLanguage = HttpContext.Current.Request["epslanguage"];

                        if(requestedLanguage!=page.LanguageBranch)
                        {
                            url = page.LinkURL.Replace(string.Format("epslanguage={0}", requestedLanguage),
                                                                 string.Format("epslanguage={0}", page.LanguageBranch));
                        }
                        else
                        {
                            url = page.LinkURL;
                        }
                    }
                    else
                    {
                        url = page.LinkURL; // Standard, the current page's URL    
                    }
                    break;
            }

            if(friendlyUrl)
            {
                url = GetFriendlyUrl(url);
            }

            return includeHost ? string.Concat(Settings.Instance.SiteUrl.GetLeftPart(UriPartial.Authority), url) : url;
        }

        /// <summary>
        /// Returns true if the page is published and should be visible to visitors
        /// </summary>
        /// <param name="page"></param>
        /// <returns></returns>
        public static bool IsPublished(this PageData page)
        {
            if (page.Status != VersionStatus.Published)
            {
                return false;
            }

            return page.StartPublish <= DateTime.Now && page.StopPublish >= DateTime.Now;
        }

        /// <summary>
        /// Provides shorthand for DataFactory.Instance.Save() method
        /// </summary>
        /// <param name="page"></param>
        /// <param name="action"></param>
        /// <param name="access"></param>
        public static PageReference Save(this PageData page, SaveAction action, AccessLevel access)
        {
            return DataFactory.Instance.Save(page, action, access);
        }

        #endregion

        #region .NET extensions

        /// <summary>
        /// Returns the quarter, a number between 1 and 4
        /// </summary>
        /// <param name="dt"></param>
        /// <returns></returns>
        public static int GetQuarter(this DateTime dt)
        {
            return (int)Math.Round((double)(dt.Month+1)/3,0,MidpointRounding.AwayFromZero);
        }

        /// <summary>
        /// Used to identify which control triggered a postback
        /// </summary>
        /// <param name="page"></param>
        /// <returns></returns>
        public static Control GetPostBackControl(this Page page)
        {
            string ctrlname = page.Request.Params.Get("__EVENTTARGET");
            
            if (!string.IsNullOrEmpty(ctrlname))
            {
                return page.FindControl(ctrlname);
            }

            foreach (string ctl in page.Request.Form)
            {
                try
                {
                    var c = page.FindControl(ctl);

                    if (c!=null && (c is System.Web.UI.WebControls.Button || c is EPiServer.UI.WebControls.ToolButton))
                    {
                        return c;
                    }
                }
                catch (NullReferenceException) { }
            }

            return null;
        }

        /// <summary>
        /// Converts a string to a UTF-8 byte array
        /// </summary>
        /// <param name="stringToConvert"></param>
        /// <returns></returns>
        public static byte[] ToByteArray(this string stringToConvert)
        {
            return new UTF8Encoding().GetBytes(stringToConvert);
        }

        #endregion
    }
}
